﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Configuration;
using System.Configuration;
using WcfContrib.Extensions.Inspectors;
using WcfContrib.Extensions.OutputControllers;
using WcfContrib.Extensions.Invokers;

namespace WcfContrib.Extensions
{
    /// <summary>
    /// Represents a service behavior for attaching infrastructure extensions.
    /// </summary>
    public class ServiceExtensionsSelectorAttribute : Attribute, IServiceBehavior
    {
        internal const KnownOutputControllers DefaultOutputControllers = KnownOutputControllers.Console | KnownOutputControllers.File;
        internal const ActivationOption DefaultTracingActivation = ActivationOption.UriMatch;
        internal const ActivationOption DefaultMockingActivation = ActivationOption.UriMatch;

        class ServiceExtensionsInfo
        {
            public List<IOutputInspector> Inspectors { get; set; }
            public List<IOutputController> Controllers { get; set; }
        }

        static Dictionary<string, ServiceExtensionsInfo> serviceExtensionsStore
            = new Dictionary<string, ServiceExtensionsInfo>();

        bool prefixHadSet;

        /// <summary>
        /// Gets or sets the activation option for the tracing extension
        /// </summary>
        public ActivationOption TracingActivation { get; set; }
        /// <summary>
        /// Gets or sets the activation option for the mocking extension
        /// </summary>
        public ActivationOption MockingActivation { get; set; }
        /// <summary>
        /// Gets or sets the duration limit in miliseconds where a notice will be issued to the output controllers
        /// </summary>
        public int DurationNoticeLimitInMs { get; set; }
        /// <summary>
        /// Gets or sets the output controllers for extension messages
        /// </summary>
        public KnownOutputControllers KnownOutputControllers { get; set; }
        /// <summary>
        /// Gets or sets the desired parameter detail to be flushed to the output controllers
        /// </summary>
        public ParameterDetail ParameterDetail { get; set; }
        /// <summary>
        /// Gets or sets the parameter serialization depth to use if serialization is needed
        /// </summary>
        public int ParameterSerializationDepth { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceExtensionsSelectorAttribute"/> class.
        /// </summary>
        public ServiceExtensionsSelectorAttribute()
        {
            TracingActivation = DefaultTracingActivation;
            MockingActivation = DefaultMockingActivation;
            DurationNoticeLimitInMs = MessageTraceInspector.DefaultDurationLimitMs;
            ParameterDetail = MessageTraceInspector.DefaultParameterDetail;
            ParameterSerializationDepth = MessageTraceInspector.DefaultParameterSerializationDepth;
            KnownOutputControllers = DefaultOutputControllers;
        }

        static string GetKey(string serviceNamespace, string serviceName)
        {
            return serviceNamespace + serviceName;
        }

        internal static IEnumerable<IOutputInspector> GetInspectors(string serviceNamespace, string serviceName)
        {
            string key = GetKey(serviceNamespace, serviceName);
            
            ServiceExtensionsInfo info;
            if (serviceExtensionsStore.TryGetValue(key, out info))
            {
                return info.Inspectors;
            }

            return null;
        }

        internal static IEnumerable<IOutputController> GetControllers(string serviceNamespace, string serviceName)
        {
            string key = GetKey(serviceNamespace, serviceName);

            ServiceExtensionsInfo info;
            if (serviceExtensionsStore.TryGetValue(key, out info))
            {
                return info.Controllers;
            }

            return null;
        }

        #region IServiceBehavior Members

        void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            if (!prefixHadSet)
            {
                if (TracingActivation == ActivationOption.UriMatch
                    || MockingActivation == ActivationOption.UriMatch)
                {
                    ServiceBehaviorAttribute behavior = serviceDescription.Behaviors.Find<ServiceBehaviorAttribute>();

                    if (behavior.AddressFilterMode == AddressFilterMode.Exact)
                    {
                        behavior.AddressFilterMode = AddressFilterMode.Prefix;
                    }
                }

                prefixHadSet = true;
            }
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            string key = GetKey(serviceDescription.Namespace, serviceDescription.Name);

            List<IOutputInspector> inspectors = new List<IOutputInspector>();
            if (TracingActivation != ActivationOption.Disabled)
            {
                inspectors.Add(new MessageTraceInspector(TracingActivation,
                    DurationNoticeLimitInMs, ParameterDetail, ParameterSerializationDepth));
            }

            List<IOutputController> controllers = new List<IOutputController>();
            if (HasKnownController(KnownOutputControllers.Console))
            {
                controllers.Add(new ConsoleOutputController());
            }
            if (HasKnownController(KnownOutputControllers.File))
            {
                controllers.Add(new FileOutputController());
            }

            ServiceExtensionsInfo info = new ServiceExtensionsInfo
            {
                Inspectors = inspectors,
                Controllers = controllers
            };

            serviceExtensionsStore.Add(key, info);

            if (MockingActivation != ActivationOption.Disabled)
            {
                serviceDescription.Behaviors.Add(
                    new MockingInvokerBehaviorAttribute(MockingActivation));
            }
        }

        void IServiceBehavior.Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
        }

        bool HasKnownController(KnownOutputControllers arg)
        {
            return ((KnownOutputControllers & arg) == arg);
        }

        #endregion
    }

    /// <summary>
    /// Represents a behavior element for defining service extensions through the configuration.
    /// </summary>
    public class ServiceExtensionsBehaviorElement : BehaviorExtensionElement
    {
        const KnownOutputControllers DefaultConfigControllers = KnownOutputControllers.Console | KnownOutputControllers.File;

        /// <summary>
        /// Gets or sets the activation option for the tracing extension
        /// </summary>
        [ConfigurationProperty("tracingActivation", DefaultValue = ServiceExtensionsSelectorAttribute.DefaultTracingActivation)]
        public ActivationOption TracingActivation
        {
            get
            {
                return (ActivationOption)base["tracingActivation"];
            }
            set
            {
                base["tracingActivation"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the activation option for the mocking extension
        /// </summary>
        [ConfigurationProperty("mockingActivation", DefaultValue = ServiceExtensionsSelectorAttribute.DefaultMockingActivation)]
        public ActivationOption MockingActivation
        {
            get
            {
                return (ActivationOption)base["mockingActivation"];
            }
            set
            {
                base["mockingActivation"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the output controllers for extension messages
        /// </summary>
        [ConfigurationProperty("knownOutputControllers", DefaultValue = DefaultConfigControllers)]
        public KnownOutputControllers KnownOutputControllers
        {
            get
            {
                return (KnownOutputControllers)base["knownOutputControllers"];
            }
            set
            {
                base["knownOutputControllers"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the duration limit in miliseconds where a notice will be issued to the output controllers
        /// </summary>
        [ConfigurationProperty("durationNoticeLimitInMs", DefaultValue = MessageTraceInspector.DefaultDurationLimitMs)]
        public int DurationNoticeLimitInMs
        {
            get
            {
                return (int)base["durationNoticeLimitInMs"];
            }
            set
            {
                base["durationNoticeLimitInMs"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the desired parameter detail to be flushed to the output controllers
        /// </summary>
        [ConfigurationProperty("parameterDetail", DefaultValue = MessageTraceInspector.DefaultParameterDetail)]
        public ParameterDetail ParameterDetail
        {
            get
            {
                return (ParameterDetail)base["parameterDetail"];
            }
            set
            {
                base["parameterDetail"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the parameter serialization depth to use if serialization is needed
        /// </summary>
        [ConfigurationProperty("parameterSerializationDepth", DefaultValue = MessageTraceInspector.DefaultParameterSerializationDepth)]
        public int ParameterSerializationDepth
        {
            get
            {
                return (int)base["parameterSerializationDepth"];
            }
            set
            {
                base["parameterSerializationDepth"] = value;
            }
        }

        private ConfigurationPropertyCollection properties = null;
        /// <summary>
        /// Gets the collection of properties.
        /// </summary>
        protected override System.Configuration.ConfigurationPropertyCollection Properties
        {
            get
            {
                if (properties == null)
                {
                    properties = new ConfigurationPropertyCollection();

                    properties.Add(new ConfigurationProperty("tracingActivation",
                        typeof(ActivationOption), ServiceExtensionsSelectorAttribute.DefaultTracingActivation));
                    properties.Add(new ConfigurationProperty("mockingActivation",
                        typeof(ActivationOption), ServiceExtensionsSelectorAttribute.DefaultMockingActivation));
                    properties.Add(new ConfigurationProperty("knownOutputControllers",
                        typeof(KnownOutputControllers), DefaultConfigControllers));
                    properties.Add(new ConfigurationProperty("durationNoticeLimitInMs",
                        typeof(int), MessageTraceInspector.DefaultDurationLimitMs));
                    properties.Add(new ConfigurationProperty("parameterDetail",
                        typeof(ParameterDetail), MessageTraceInspector.DefaultParameterDetail));
                    properties.Add(new ConfigurationProperty("parameterSerializationDepth",
                        typeof(int), MessageTraceInspector.DefaultParameterSerializationDepth));
                }

                return properties;
            }
        }

        /// <summary>
        /// Gets the type of behavior.
        /// </summary>
        public override Type BehaviorType
        {
            get { return typeof(ServiceExtensionsSelectorAttribute); }
        }

        /// <summary>
        /// Creates a behavior extension based on the current configuration settings.
        /// </summary>
        /// <returns></returns>
        protected override object CreateBehavior()
        {
            return new ServiceExtensionsSelectorAttribute
            {
                TracingActivation = TracingActivation,
                MockingActivation = MockingActivation,
                KnownOutputControllers = KnownOutputControllers,
                DurationNoticeLimitInMs = DurationNoticeLimitInMs,
                ParameterDetail = ParameterDetail,
                ParameterSerializationDepth = ParameterSerializationDepth
            };
        }
    }
}
